package space.cloaking.utils;

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.Random;

/**
 * 用于生成特定格式的码
 */
public abstract class CodeGenerator {

    /**
     * 20位码，{4位年}{2位月份}{2位日}{2位小时，0~23}{2位分钟}{2位秒}{6位流水，从000001开始}
     * <p/>
     *
     * 即 1 秒钟内允许生成 1e6 - 1 个码, 超过则会抛出 {@link IllegalStateException}
     * <p/>
     *
     * 这个实现所使用的时间戳记录和流水号记录均记录在内存中，既认定服务起关闭后再开启超过时间戳分别率
     * 1 秒，在这个前提下不会产生重复的码
     */
    public static final int CODE_20 = 1;

    /**
     * 类似于 {@link #CODE_20}, 在 {@link #CODE_20} 的基础上6位流水号做简单的异或混淆
     */
    public static final int CODE_20_MIX6 = 2;

    /**
     * 创建实例, format 如 {@link #CODE_20 CodeGenerator.CODE_20}
     */
    public static CodeGenerator newInstance(int format) {
        switch(format) {
        case CodeGenerator.CODE_20 :
            return new Code20Impl(false);
        case CODE_20_MIX6 :
            return new Code20Impl(true);
        }

        throw new IllegalArgumentException("没有这个格式：" + format);
    }

    /**
     * 生成下一个码
     */
    public abstract String generate();

    private static class Code20Impl extends CodeGenerator {

        static ThreadLocal<DateFormat> sDateFormat = ThreadLocal.withInitial(() ->
                new SimpleDateFormat("yyyyMMddHHmmss", Locale.getDefault()));

        static ThreadLocal<Date> sDate = ThreadLocal.withInitial(Date::new);

        /**
         * 记录时间戳
         */
        private volatile long mTimeStamp;

        /**
         * 记录流水号
         */
        private volatile int mSerialNumber = 1;

        /**
         * 异或用的模板
         */
        private volatile int mMixTemplate;

        private final boolean mMix;
        private final Random mRandom;

        Code20Impl(boolean mix) {
            mMix = mix;
            mRandom = mix ? new Random() : null;
            if(mix) {
                mMixTemplate = mRandom.nextInt();
            }
        }

        @Override
        public String generate() {
            long now = System.currentTimeMillis();
            long stamp = now / 1000;

            int serial;
            int template = 0;

            synchronized(this) {
                if(stamp == mTimeStamp) {
                    serial = mSerialNumber;
                    mSerialNumber = serial + 1;
                    if(mMix) {
                        template = mMixTemplate;
                    }
                } else {
                    mTimeStamp = stamp;
                    serial = 1;
                    mSerialNumber = 2;
                    if(mMix) {
                        template = mRandom.nextInt();
                        template &= 0xfffff;    // 20 位足够 1e6-1
                        mMixTemplate = template;
                    }
                }
            }

            if(serial >= 1e6) {
                throw new IllegalStateException("流水号溢出 ！！");
            }

            Date date = sDate.get();
            date.setTime(now);

            if(mMix) {
                serial ^= template;
                if(serial > 999999) {
                    serial &= 0x7ffff;    // 不超过 1e6-1
                }
            }

            return sDateFormat.get().format(date) + String.format("%1$06d", serial);
        }
    }
}
